// Author: wsfuyibing <682805@qq.com>
// Date: 2025-04-11

package commands

import (
	"errors"
	"fmt"
	"gitee.com/go-libs/console"
	"os"
	"path/filepath"
	"regexp"
	"time"
)

var (
	regexMatchTemplateVariable = regexp.MustCompile(`\{\{([._a-zA-Z0-9-]+)}}`)
)

// Provider
// is a common component, used for child command providers.
type Provider struct {
	// ModuleName
	// it's read from go.mod file and match module paragraph.
	//
	//   - sketch
	//   - github.com/fuyibing/sketch
	ModuleName string

	// ModulePath
	// it's an absolute path that go.mod file position.
	ModulePath string

	WorkingDir string
}

func (o *Provider) InitModule() (err error) {
	var (
		body     []byte
		info     os.FileInfo
		path     string
		regexMod = regexp.MustCompile(`module\s+(\S+)\n`)
		regexWin = regexp.MustCompile(`^[a-zA-Z]:`)
		src      string
	)

	// Initialize working directory.
	if err = o.InitWorkingDir(); err != nil {
		return
	}

	// Recursive directory until the target file (go.mod) is found.
	path = o.WorkingDir
	for {
		if regexWin.MatchString(path) || path == "/" {
			err = fmt.Errorf(`go.mod: not found`)
			return
		}

		// Build go mod file path.
		src = fmt.Sprintf(`%s/%s`, path, `go.mod`)

		// Not found.
		if info, err = os.Stat(src); err != nil {
			if errors.Is(err, os.ErrNotExist) {
				path, err = filepath.Abs(path + "/../")
				continue
			} else {
				return
			}
		}

		// Found.
		if info.IsDir() {
			err = fmt.Errorf(`go.mod: can not be a directory`)
			return
		}

		// Read content from go.mod.
		if body, err = os.ReadFile(src); err != nil {
			return
		}

		// Parse module path.
		o.ModulePath = path

		// Parse module name.
		if m := regexMod.FindStringSubmatch(string(body)); len(m) > 0 {
			o.ModuleName = m[1]
		} else {
			err = fmt.Errorf(`go.mod: can not find module definition`)
		}
		break
	}
	return
}

func (o *Provider) InitTemplateData(cmd *console.Command) map[string]any {
	return map[string]any{
		"Datetime":   time.Now().Format(time.DateTime),
		"ModuleName": o.ModuleName,
		"Script":     cmd.ToScript(),
	}
}

func (o *Provider) InitWorkingDir() (err error) {
	o.WorkingDir, err = filepath.Abs("./")
	return
}

func (o *Provider) ParseTemplateData(text string, data map[string]any) string {
	return regexMatchTemplateVariable.ReplaceAllStringFunc(text, func(s string) string {
		if m := regexMatchTemplateVariable.FindStringSubmatch(s); len(m) > 0 {
			if v, ok := data[m[1]]; ok {
				return fmt.Sprintf("%v", v)
			}
		}
		return ""
	})
}

func (o *Provider) CheckOverride(src string, override bool) (ignored bool, err error) {
	var info os.FileInfo

	// Do not ignore if override enabled.
	if override {
		return
	}

	// Error occurred on stat.
	if info, err = os.Stat(src); err != nil {
		// Internal error.
		if !errors.Is(err, os.ErrNotExist) {
			ignored = true
			return
		}

		// Create for file not exists.
		err = nil
		return
	} else {
		ignored = true
	}

	// Ignore if target is a directory.
	if info.IsDir() {
		err = fmt.Errorf(`target not a valid file`)
	}
	return
}

func (o *Provider) SaveFile(src, text string, data map[string]any) (err error) {
	var (
		file *os.File
		info os.FileInfo
		path = regexp.MustCompile(`/[^/]+$`).ReplaceAllString(src, "")
	)

	// Directory verify.
	if info, err = os.Stat(path); err != nil {
		if !errors.Is(err, os.ErrNotExist) {
			return
		}

		// Create directory.
		err = os.MkdirAll(path, os.ModePerm)
	} else if !info.IsDir() {
		err = fmt.Errorf(`%s: not a directory`, path)
		return
	}

	// Open target file, create if not exists and truncate if exists.
	if file, err = os.OpenFile(src, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm); err != nil {
		return
	}

	// Close file describer when done.
	defer func() {
		_ = file.Close()
	}()

	// Write content to target file.
	_, err = file.WriteString(o.ParseTemplateData(text, data))
	return
}
